﻿using System.Collections.Generic;
using System.Collections.ObjectModel;
using MathNet.Numerics.Transformations;
using System.Linq;
using System;
using System.Threading;
using System.Windows;

namespace RytmSerca.Ekg
{
    /// <summary>
    /// reprezentacja sygnału EKG
    /// </summary>
    internal class EkgSignal : ObservableCollection<EkgPoint>
    {
        List<EkgPoint> _rPeaks = new List<EkgPoint>();
        double _beatsPerMinute = 0;

        public double BeatsPerMinute
        {
            get { return _beatsPerMinute; }
        }

        public List<EkgPoint> RPeaks
        {
            get { return _rPeaks; }
            set { _rPeaks = value; }
        }
        RealFourierTransformation _fourier = new RealFourierTransformation();

        public void AddRange(IEnumerable<EkgPoint> ekgPoints)
        {
            foreach (EkgPoint ekgPoint in ekgPoints)
                Add(ekgPoint);
        }
        double _max = double.MinValue;
        double _min = double.MaxValue;

        double _endTime = 0.0;

        public double Max { get { return _max; } }
        public double Min { get { return _min; } }
        public double EndTime { get { return _endTime; } }
        public AnalyseParameters Parameters { get; set; }


        public void AdjustMaxMin()
        {
            _max = double.MinValue;
            _min = double.MaxValue;
            _endTime = 0;
            foreach (EkgPoint p in this)
            {
                _max = Math.Max(_max, p.Value);
                _min = Math.Min(_min, p.Value);
                _endTime = Math.Max(_endTime, p.Time);
            }
        }

        /// <summary>
        /// Za pomocą transformaty Fouriera odcina składowe o niskiej częstotliwości,
        /// usuwając w ten sposób falowanie całego sygnału wywołane np. oddechem.
        /// </summary>
        /// <param name="thresold">Częstotliwość, poniżej której składowe będą wyzerowane.</param>
        void EqualizeSignal(double[] samples)
        {
            double threshold = Parameters.FrequencyThreshold;
            int part = samples.Length;
            double[] fftRe, fftImg;
            _fourier.TransformForward(samples, out fftRe, out fftImg);
            int i;

            for (i = 0; i < threshold; i++)
            {
                fftRe[i] = 0;
                fftRe[part - i - 1] = 0;
                fftImg[i] = 0;
                fftImg[part - i - 1] = 0;
            }
            double[] mod;
            _fourier.TransformBackward(fftRe, fftImg, out mod);
            mod.CopyTo(samples, 0);
            if (Parameters.ReturnFilteredSignal)
            {
                for (i = 0; i < mod.Length; i++)
                {
                    this[i].Value = mod[i];
                }
                AdjustMaxMin();
            }
        }

        /// <summary>
        /// Przeprowadza WindowedMaxFilter dla sygnału.
        /// </summary>
        /// <param name="windowWidth">Szerokość okna, w którym szukane jest maximum. Powinno odpowiadać szerokości
        /// załamka R.</param>
        /// <param name="threshold">Minimalna wartość, jaką może przyjąć szczyt załamka R. Napięcia poniżej
        /// tej wartości będą ignorowane.</param>
        int[] DetectRPeaks(double[] samples)
        {
            double windowWidth = Parameters.WindowWidth;
            double threshold = Parameters.RPeakMinimalHeight;
            if (Count == 0)
                return new int[0];
            List<int> peaks = new List<int>();

            int maxNo = -1;
            for (int i = 0; i < samples.Length; i++)
            {
                double value = samples[i];

                if ((maxNo == -1 || value > samples[maxNo]) && value > threshold)
                    maxNo = i;
                if (maxNo != -1 && TimeOf(i) - TimeOf(maxNo) > windowWidth)
                {
                    peaks.Add(maxNo);
                    maxNo = -1;
                }
            }
            if (maxNo != -1)
                peaks.Add(maxNo);
            return peaks.ToArray();
        }
        double TimeOf(int sampleNo)
        {
            return this[sampleNo].Time;
        }

        double CalculateHeartBeat(int[] rPeakSamples)
        {
            if (rPeakSamples.Length < 2)
            {
                return 0;
            }

            double sum = 0;
            for (int i = 1; i < rPeakSamples.Length; i++)
            {
                sum += (TimeOf(rPeakSamples[i]) - TimeOf(rPeakSamples[i - 1]));
            }
            double delta = sum / (rPeakSamples.Length - 1);
            return 60.0 / delta;
        }

        public void Analyse()
        {
            int part = (int)Math.Pow(2, Math.Floor(Math.Log(Count, 2)));
            double[] samples = this.Take(part).Select(ekgPoint => ekgPoint.Value).ToArray();
            //Thread.Sleep(3000);

            EqualizeSignal(samples);

            int[] rPeaks = DetectRPeaks(samples);
            _beatsPerMinute = CalculateHeartBeat(rPeaks);

            _rPeaks.Clear();
            for (int i = 0; i < rPeaks.Length; i++)
                _rPeaks.Add(this[rPeaks[i]]);
            // FOR TESTING
            //for (int i = 0; i < part; i++)
            //  this[i].Value = samples[i];         
        }

    }
    public class AnalyseParameters
    {
        double _frequencyThreshold = 30;
        public double FrequencyThreshold
        {
            get { return _frequencyThreshold; }
            set { _frequencyThreshold = value; }
        }
        double _rPeakMinimalHeight = 100;
        public double RPeakMinimalHeight
        {
            get { return _rPeakMinimalHeight; }
            set { _rPeakMinimalHeight = value; }
        }
        double _windowWidth = 0.4;
        public double WindowWidth
        {
            get { return _windowWidth; }
            set { _windowWidth = value; }
        }
        bool _returnFilteredSignal = false;

        public bool ReturnFilteredSignal
        {
            get { return _returnFilteredSignal; }
            set { _returnFilteredSignal = value; }
        }
    }

}